package com.wrupple.muba.catalogs.client.widgets.editors.composite;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Provider;
import com.wrupple.muba.catalogs.client.cms.service.CatalogManagementSystem;
import com.wrupple.muba.catalogs.client.module.services.logic.CatalogDescriptionService;
import com.wrupple.muba.catalogs.client.module.services.logic.FieldConversionStrategyProvider;
import com.wrupple.muba.catalogs.client.module.services.logic.FieldDescriptionService;
import com.wrupple.muba.catalogs.client.module.services.presentation.GenericFieldFactory;
import com.wrupple.muba.catalogs.client.module.services.presentation.impl.JavaScriptAdapter.StringJSOadapter;
import com.wrupple.muba.catalogs.client.widgets.editors.ForeignValueRelationEditor.RelationshipDelegate;
import com.wrupple.muba.catalogs.client.widgets.editors.composite.delegates.ManyToOneRelationshipDelegate;
import com.wrupple.muba.catalogs.client.widgets.editors.composite.delegates.OneToManyRelationshipDelegate;
import com.wrupple.muba.catalogs.client.widgets.editors.composite.delegates.OneToOneRelationshipDelegate;
import com.wrupple.muba.catalogs.client.widgets.editors.impl.SimpleCatalogEditor;
import com.wrupple.muba.catalogs.domain.CatalogEntry;
import com.wrupple.muba.catalogs.domain.JsCatalogKey;
import com.wrupple.muba.common.client.FieldConversionStrategy;
import com.wrupple.muba.common.client.JSOHelper;
import com.wrupple.muba.common.client.application.DataCallback;
import com.wrupple.muba.common.domain.FilterData;
import com.wrupple.muba.common.shared.FilterDataUtils;
import com.wrupple.muba.common.shared.State.ProcessManager;
import com.wrupple.muba.common.shared.State.StorageManager;
import com.wrupple.muba.common.shared.StateTransition;
import com.wrupple.vegetate.client.util.JsArrayList;
import com.wrupple.vegetate.domain.CatalogDescriptor;
import com.wrupple.vegetate.domain.FieldDescriptor;

public abstract class CompositeCatalogEditor<V extends JavaScriptObject>
		extends SimpleCatalogEditor<V> {

	protected FieldDescriptionService fieldService;
	V currentEntry;
	private V originalEntry;
	protected Map<String, HasValue<Object>> fields;
	private boolean collectFieldChangeEvents;
	private boolean collectFieldValuesFromPlace;
	private CatalogDescriptor catalog;
	private CatalogDescriptionService catalogService;
	protected FieldConversionStrategyProvider conversionService;
	protected GenericFieldFactory fieldFactory;
	protected StorageManager sm;
	private CatalogManagementSystem cms;
	private Provider<FilterData> addFilterProvider;

	public CompositeCatalogEditor(CatalogManagementSystem cms,StorageManager sm,
			FieldDescriptionService fieldService,
			CatalogDescriptionService catalogSErvice,
			FieldConversionStrategyProvider conversion,
			GenericFieldFactory fieldDactory) {
		super();
		this.cms=cms;
		this.sm = sm;
		this.conversionService = conversion;
		collectFieldChangeEvents = false;
		this.fieldService = fieldService;
		this.catalogService = catalogSErvice;
		this.fieldFactory = fieldDactory;
		fields = new HashMap<String, HasValue<Object>>();
		this.addFilterProvider = new Provider<FilterData>(){
			@Override
			public FilterData get() {
				return new FilterData();
			}
			
		};
	}

	@Override
	public V getValue() {
		assert getCatalog() != null : "This forms haz not been initialized";

		Set<String> fieldNames = fields.keySet();
		V value = getOrCreateOriginalEntry();
		Object fieldValue;
		FieldConversionStrategy conversion;
		for (String fieldid : fieldNames) {
			fieldValue = getFieldValue(fieldid);
			conversion = conversionService.getFieldConversionStrategy(fieldid,
					getCatalog());
			JSOHelper.setAttribute(value, fieldid, fieldValue, conversion);
		}
		currentEntry = value;
		CatalogEntry.setCatalog(value, getCatalog().getId());
		return value;
	}

	@Override
	public void setValue(V value) {
		this.originalEntry = value;
		catalogService.loadCatalogDescriptor(catalogid, new SetValue());
	}

	/**
	 * FIXME this method is HIGHLY ineficient, even for client-side standards
	 * @param entry
	 */
	public void renderAllFields(V entry,Set<String> fieldNames) {
		assert getCatalog() != null : "This forms haz not been initialized";
		this.currentEntry = entry;

		Object fieldValue;
		HasValue field;
		FieldDescriptor fdescriptor;
		FieldConversionStrategy conversion;
		RelationshipDelegate relationshipDelegate;
		JSONValue wrappingValue;
		for (String fieldid : fieldNames) {
			fdescriptor = getCatalog().getFieldDescriptor(fieldid);
			conversion = conversionService.getFieldConversionStrategy(fieldid,
					getCatalog());
			relationshipDelegate=null;
			if (fdescriptor.isKey()) {
				wrappingValue= JSOHelper.getObjectValue(fieldid, entry);
				if(wrappingValue.isArray()!=null){
					relationshipDelegate = new OneToManyRelationshipDelegate(this,fdescriptor,cms,this.sm,addFilterProvider,"delete?");
				}else{
					relationshipDelegate = new OneToOneRelationshipDelegate(this,fdescriptor,cms,this.sm,addFilterProvider,"delete?");
				}
			} else {
				if(fdescriptor.isEphemeral()){
					relationshipDelegate = new ManyToOneRelationshipDelegate(catalogService,bus,pm,sm,cms,fdescriptor,catalog.getId(),JSOHelper.getAttribute(entry, catalog.getKeyField()));
				}
			}
			fieldValue = JSOHelper.getAttributeAsObject(entry, fieldid,
					conversion);
			field = fields.get(fieldid);

			if (field == null) {
				
				field = this.fieldFactory.getOrCreateField(fdescriptor, mode,
						bus, pm,relationshipDelegate);

				fields.put(fieldid, field);
				if (collectFieldChangeEvents && field != null) {
					field.addValueChangeHandler(new FieldChangeHandler(fieldid,
							conversion));
				}
			}
			this.maybeAddField(field, fdescriptor);
			//force string outcome
			if ("text".equals(fdescriptor.getWidget())) {
				if (fieldValue != null) {
					// FIXME know when to force a String outcome
					fieldValue = fieldValue.toString();
				}
			}
			//switch to filterable value
			if(fdescriptor.isKey() || fdescriptor.isEphemeral()){
				if(fdescriptor.isKey()){
					fieldValue = buildOneToManyFilterData(fieldValue);
				}else{
					field = new EphemeralFieldWrapper(field,fdescriptor);
				}
			}

			if (field == null) {
				// ignore
			} else {
				if (fieldValue == null) {
					if (fdescriptor.getDefaultValue() != null) {
						fieldValue = StringJSOadapter.performValueTransformation(fdescriptor.getDefaultValue());
					}
				}
				field.setValue(fieldValue);
			}

		}
	}

	private FilterData buildOneToManyFilterData(Object fieldValue) {
		if(fieldValue==null){
			return null;
		}else{
			FilterData regreso = new FilterData();
			if(fieldValue instanceof String){
				String value = (String) fieldValue;
				return FilterDataUtils.createSingleFieldFilter(JsCatalogKey.ID_FIELD, value);
			}else{
				JsArrayString value = (JsArrayString) fieldValue;
				return FilterDataUtils.createSingleFieldFilter(JsCatalogKey.ID_FIELD, value);
			}
		}
	}

	protected abstract void maybeAddField(HasValue<Object> field,
			FieldDescriptor fdescriptor);

	protected void fireChangeEvent(V current) {
		ValueChangeEvent.fire(this, current);
	}

	public V getCurrentEntry() {
		if (currentEntry == null) {
			currentEntry = getOrCreateOriginalEntry();
		}
		return currentEntry;
	}

	protected Object getFieldValue(String fieldid) {
		HasValue<?> field = fields.get(fieldid);
		return field == null ? null : field.getValue();
	}

	protected V getOrCreateOriginalEntry() {
		if (originalEntry == null) {
			originalEntry = CatalogEntry.createCatalogEntry(getCatalog().getId())
					.cast();
		}
		return originalEntry;
	}

	protected void setCollectFieldChangeEvents(boolean collectFieldChangeEvents) {
		this.collectFieldChangeEvents = collectFieldChangeEvents;
	}


	private void findEphemeralValue(String localKey, String foreignCatalogId,
			String foreignKeyField, final StateTransition callback) {
		FilterData filterData = FilterDataUtils.createSingleFieldFilter(
				foreignKeyField, localKey);

		sm.read(foreignCatalogId, filterData, callback);

	}

	public void fetchForeignEntries(JsArrayString array,
			String foreignCatalog, StateTransition foreignFieldCallback) {
		List<String> ids = JsArrayList.arrayAsListOfString(array);
		FilterData filterData = FilterDataUtils.createSingleFieldFilter("id",
				ids);
		sm.read(foreignCatalog, filterData, foreignFieldCallback);
	}

	public CatalogDescriptor getCatalog() {
		return catalog;
	}
	
	public ProcessManager getProcessManager() {
		return pm;
	}

	public com.google.gwt.event.shared.EventBus getEventBus() {
		return bus;
	}



	class FieldChangeHandler implements ValueChangeHandler<Object> {

		private String field;
		private FieldConversionStrategy conversionStrategy;

		public FieldChangeHandler(String field,
				FieldConversionStrategy conversionStrategy) {
			super();
			this.field = field;
			this.conversionStrategy = conversionStrategy;
		}

		@Override
		public void onValueChange(ValueChangeEvent<Object> event) {
			V current = getCurrentEntry();
			Object value = event.getValue();
			JSOHelper.setAttribute(current, field, value, conversionStrategy);
			fireChangeEvent(current);
		}

	}

	class SetValue extends DataCallback<CatalogDescriptor> {

		@Override
		public void execute() {
			catalog = result;
			
			
			V value = originalEntry;
			if (collectFieldValuesFromPlace) {
				if (value == null) {
					value = getOrCreateOriginalEntry();
				}
			}
			Set<String> fieldNames;
			switch (mode) {
			case CREATE:
				fieldNames = fieldService.getCreateDescriptors(getCatalog())
						.keySet();
				break;
			case UPDATE:
				fieldNames = fieldService.getUpdateDescriptors(getCatalog())
						.keySet();
				break;
			default:
				fieldNames = fieldService.getDetailDescriptors(getCatalog()).keySet();
				break;
			}
			
			renderAllFields(value,fieldNames);
			fireChangeEvent(value);
		}
	}

	class FindForeignCatalogEphemeralValuesCallback extends DataCallback<CatalogDescriptor> {

		String localCatalog;
		private String localKey;
		private StateTransition<JsArray<JsCatalogKey>> callback;

		public FindForeignCatalogEphemeralValuesCallback(String localKey, String localCatalog,
				StateTransition<JsArray<JsCatalogKey>> foreignFieldCallback) {
			this.localKey = localKey;
			this.localCatalog = localCatalog;
			this.callback = foreignFieldCallback;
		}

		@Override
		public void execute() {
			assert result != null : "Ephemeral field points to null value";
			Collection<FieldDescriptor> foreignFields = result.getFields();

			String fieldsForeignCatalog;
			String foreignKeyField;
			for (FieldDescriptor field : foreignFields) {
				fieldsForeignCatalog = field.getForeignCatalog();
				if (localCatalog.equals(fieldsForeignCatalog)) {
					foreignKeyField = field.getId();
					findEphemeralValue(localKey, result.getId(),
							foreignKeyField, callback);
				}
			}

		}

	}
	
	class SetEphemeralFilterData extends DataCallback<CatalogDescriptor>{
		HasValue<FilterData> wrapped;
		
		public SetEphemeralFilterData(HasValue<FilterData> wrapped) {
			super();
			this.wrapped = wrapped;
		}

		@Override
		public void execute() {
			Collection<FieldDescriptor> allFields = result.getFields();
			String localKey = JSOHelper.getAttribute(currentEntry, catalog.getKeyField());
			for(FieldDescriptor fd : allFields){
				if(catalog.getId().equals(fd.getForeignCatalog())){
					FilterData filterData = FilterDataUtils.createSingleFieldFilter(fd.getId(), localKey);
					wrapped.setValue(filterData);
				}
			}
		}
		
	}

	class EphemeralFieldWrapper implements IsWidget, HasValue<Object> {
		
		HasValue<FilterData> wrapped;
		FieldDescriptor descriptor;

		public EphemeralFieldWrapper(HasValue<FilterData> wrapped, FieldDescriptor descriptor) {
			super();
			this.wrapped = wrapped;
			this.descriptor = descriptor;
			String foreignCatalog = descriptor.getForeignCatalog();
			catalogService.loadCatalogDescriptor(foreignCatalog, new SetEphemeralFilterData(wrapped));
		}

		@Override
		public void setValue(Object value) {
			//whatever value is irrelevant
		}
		
		@Override
		public void setValue(Object value, boolean fireEvents) {
			setValue(value);
		}
		
		@Override
		public HandlerRegistration addValueChangeHandler(
				ValueChangeHandler handler) {
			return wrapped.addValueChangeHandler(handler);
		}

		@Override
		public void fireEvent(GwtEvent<?> event) {
			wrapped.fireEvent(event);
		}

		@Override
		public Object getValue() {
			return null;
		}

		@Override
		public Widget asWidget() {
			return (Widget) wrapped;
		}
		
		
	}

}
